#include "activations.h"

static inline Float StairActivate(Float x) {
    int n = floor(x);
    if (n % 2 == 0) {
        return floor(x/2.0);
    } else {
        return (x - n) + floor(x/2.0);
    }
}

static inline Float StairGradient(Float x) {
    if (floor(x) == x) return 0;
    return 1;
}

static inline Float HardtanActivate(Float x) {
    if (x < -1) {
        return -1;
    }
    if (x > 1) {
        return 1;
    }
    return x;
}

static inline Float HardtanGradient(Float x) {
    if (x > -1 && x < 1) return 1;
    else return 0;
}

static inline Float LinearActivate(Float x) {
    return x;
}

static inline Float LinearGradient(Float x) {
    return 1;
}

static inline Float LogisticActivate(Float x) {
    return 1.0/(1.0 + exp(-x));
}

static inline Float LogisticGradient(Float y) {
    return (1 - y) * y;
}

static inline Float LoggyActivate(Float x) {
    return 2.0/(1.0 + exp(-x)) - 1;
}

static inline Float LoggyGradient(Float x) {
    Float y = (1.0 + x) / 2.0;
    return 2 * (1 - y)*y;
}

static inline Float ReluActivate(Float x) {
    return x * (x > 0);
}

static inline Float ReluGradient(Float x) {
    return (x > 0);
}

static inline Float EluActivate(Float x) {
    return (x >= 0) * x + (x < 0) * (exp(x) - 1);
}

static inline Float EluGradient(Float x) {
    return (x >= 0) + (x < 0) * (x + 1);
}

static inline Float RelieActivate(Float x) {
    return (x > 0) ? x: 0.01 * x;
}

static inline Float RelieGradient(Float x) {
    return (x > 0) ? 1 : 0.01;
}

static inline Float RampActivate(Float x) {
    return x * (x > 0) + 0.1 * x;
}

static inline Float RampGradient(Float x) {
    return (x > 0) + 0.1;
}

static inline Float LeakyActivate(Float x) {
    return (x > 0) ? x  : 0.1 * x;
}

static inline Float LeakyGradient(Float x) {
    return (x > 0) ? 1 : 0.1;
}

static inline Float TanhActivate(Float x) {
    return (exp(2*x) - 1) / (exp(2*x) + 1);
}

static inline Float TanhGradient(Float x) {
    return 1 - x * x;
}

static inline Float PlseActivate(Float x) {
    if (x < -4) return 0.01 * (x + 4);
    if (x > 4) return 0.01 * (x - 4) + 1;
    return 0.125 * x + 0.5;
}

static inline Float PlseGradient(Float x) {
    return (x < 0 || x > 1) ? 0.01 : 0.125;
}

static inline Float LhtanActivate(Float x) {
    if (x < 0) return 0.001 * x;
    if (x > 1) return 0.001 * (x - 1) + 1;
    return x;
}

static inline Float LhtanGradient(Float x) {
    if (x > 0 && x < 1) return 1;
    return 0.001;
}


char *GetActivationString(Activation a) {
    switch (a)
    {
    case LOGISTIC:
            return "logistic";
        case LOGGY:
            return "loggy";
        case RELU:
            return "relu";
        case ELU:
            return "elu";
        case RELIE:
            return "relie";
        case RAMP:
            return "ramp";
        case LINEAR:
            return "linear";
        case TANH:
            return "tanh";
        case PLSE:
            return "plse";
        case LEAKY:
            return "leaky";
        case STAIR:
            return "stair";
        case HARDTAN:
            return "hardtan";
        case LHTAN:
            return "lhtan";
        default:
            break;
    }
    return "relu";
}

Activation GetActivation(char *s)
{
    if (strcmp(s, "logistic")==0) return LOGISTIC;
    if (strcmp(s, "loggy")==0) return LOGGY;
    if (strcmp(s, "relu")==0) return RELU;
    if (strcmp(s, "elu")==0) return ELU;
    if (strcmp(s, "relie")==0) return RELIE;
    if (strcmp(s, "plse")==0) return PLSE;
    if (strcmp(s, "hardtan")==0) return HARDTAN;
    if (strcmp(s, "lhtan")==0) return LHTAN;
    if (strcmp(s, "linear")==0) return LINEAR;
    if (strcmp(s, "ramp")==0) return RAMP;
    if (strcmp(s, "leaky")==0) return LEAKY;
    if (strcmp(s, "tanh")==0) return TANH;
    if (strcmp(s, "stair")==0) return STAIR;
    fprintf(stderr, "Couldn't find activation function %s, going with ReLU\n", s);
    return RELU;
}

Float Activate(Float x, Activation a)
{
    switch(a){
        case LINEAR:
            return LinearActivate(x);
        case LOGISTIC:
            return LogisticActivate(x);
        case LOGGY:
            return LoggyActivate(x);
        case RELU:
            return ReluActivate(x);
        case ELU:
            return EluActivate(x);
        case RELIE:
            return RelieActivate(x);
        case RAMP:
            return RampActivate(x);
        case LEAKY:
            return LeakyActivate(x);
        case TANH:
            return TanhActivate(x);
        case PLSE:
            return PlseActivate(x);
        case STAIR:
            return StairActivate(x);
        case HARDTAN:
            return HardtanActivate(x);
        case LHTAN:
            return LhtanActivate(x);
    }
    return 0;
}

void ActivateArray(int n, Float *x, Activation a)
{
    int i;
    for(i = 0; i < n; ++i){
        x[i] = Activate(x[i], a);
    }
}

float Gradient(Float x, Activation a)
{
    switch(a){
        case LINEAR:
            return LinearGradient(x);
        case LOGISTIC:
            return LogisticGradient(x);
        case LOGGY:
            return LoggyGradient(x);
        case RELU:
            return ReluGradient(x);
        case ELU:
            return EluGradient(x);
        case RELIE:
            return RelieGradient(x);
        case RAMP:
            return RampGradient(x);
        case LEAKY:
            return LeakyGradient(x);
        case TANH:
            return TanhGradient(x);
        case PLSE:
            return PlseGradient(x);
        case STAIR:
            return StairGradient(x);
        case HARDTAN:
            return HardtanGradient(x);
        case LHTAN:
            return LhtanGradient(x);
    }
    return 0;
}

void GradientArray(int n, Float *x, Activation a, Float *delta)
{
    int i;
    for(i = 0; i < n; ++i){
        delta[i] *= Gradient(x[i], a);
    }
}
